package drds.global_transaction.api.api;

import drds.global_transaction.api.rpc.rpc_client.Role;
import drds.global_transaction.common.Constants;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Slf4j
public class RpcContext
{

	//
	private static String getAddress(Channel channel)
	{
		SocketAddress socketAddress = channel.remoteAddress();
		String address = socketAddress.toString();
		if (socketAddress.toString().indexOf(Constants.endpoint_begin_char) == 0)
		{
			address = socketAddress.toString().substring(Constants.endpoint_begin_char.length());
		}
		return address;
	}

	private static Integer getClientPort(Channel channel)
	{
		String address = getAddress(channel);
		Integer port = 0;
		try
		{
			if (address.contains(Constants.ip_port_split_char))
			{
				port = Integer.parseInt(address.substring(address.lastIndexOf(Constants.ip_port_split_char) + 1));
			}
		} catch (NumberFormatException exx)
		{
			log.error(exx.getMessage());
		}
		return port;
	}

	@Setter
	@Getter
	private Role role;
	@Setter
	@Getter
	private String version;
	@Setter
	@Getter
	private String applicationId;
	@Setter
	@Getter
	private String transactionServiceGroup;

	@Setter
	@Getter
	private Channel channel;

	@Setter
	@Getter
	private Set<String> resourceIdSet;
	@Setter
	@Getter
	private ConcurrentMap<Channel, RpcContext> channelToRpcContextMap;

	/**
	 * transaction_manager_client
	 */
	@Setter
	@Getter
	private ConcurrentMap<Integer, RpcContext> clientPortToRpcContextMap;

	/**
	 * resource_manager_client
	 */
	@Setter
	@Getter
	private ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>> resourceIdToClientPortToRpcContextMapMap;

	//
	public void addResourceId(Set<String> resourceIdSet)
	{
		if (null == resourceIdSet)
		{
			return;
		}
		if (null == this.resourceIdSet)
		{
			this.resourceIdSet = new HashSet<String>();
		}
		this.resourceIdSet.addAll(resourceIdSet);
	}

	public String getClientIp()
	{
		String address = getAddress(channel);
		String clientIp = address;
		if (clientIp.contains(Constants.ip_port_split_char))
		{
			clientIp = clientIp.substring(0, clientIp.lastIndexOf(Constants.ip_port_split_char));
		}
		return clientIp;
	}

	public void map$channelToRpcContextMap(ConcurrentMap<Channel, RpcContext> channelToRpcContextMap)
	{
		if (this.channelToRpcContextMap != null)
		{
			throw new IllegalStateException();
		}
		this.channelToRpcContextMap = channelToRpcContextMap;
		this.channelToRpcContextMap.put(channel, this);
	}

	/**
	 * Hold in client channels.<>transaction_manager_client</>
	 */
	public void map$clientPortToRpcContextMap(ConcurrentMap<Integer, RpcContext> clientPortToRpcContextMap)
	{
		if (this.clientPortToRpcContextMap != null)
		{
			throw new IllegalArgumentException();
		}
		this.clientPortToRpcContextMap = clientPortToRpcContextMap;
		Integer clientPort = getClientPort(channel);
		this.clientPortToRpcContextMap.put(clientPort, this);
	}

	/**
	 *
	 * @param resourceId                resourceIdToClientPortToRpcContextMapMap
	 * @param clientPortToRpcContextMap
	 */
	public void map$resourceIdToClientPortToRpcContextMapMap(String resourceId, @NonNull ConcurrentMap<Integer, RpcContext> clientPortToRpcContextMap)
	{
		if (null == this.resourceIdToClientPortToRpcContextMapMap)
		{
			this.resourceIdToClientPortToRpcContextMapMap = new ConcurrentHashMap<String, ConcurrentMap<Integer, RpcContext>>();
		}
		//
		Integer clientPort = getClientPort(channel);
		clientPortToRpcContextMap.put(clientPort, this);
		//
		this.resourceIdToClientPortToRpcContextMapMap.put(resourceId, clientPortToRpcContextMap);
	}

	public void map$resourceIdToClientPortToRpcContextMapMap(String resourceId, Integer clientPort)
	{
		if (null == this.resourceIdToClientPortToRpcContextMapMap)
		{
			this.resourceIdToClientPortToRpcContextMapMap = new ConcurrentHashMap<String, ConcurrentMap<Integer, RpcContext>>();
		}
		resourceIdToClientPortToRpcContextMapMap.putIfAbsent(resourceId, new ConcurrentHashMap<Integer, RpcContext>());
		ConcurrentMap<Integer, RpcContext> clientPortToRpcContextMap = resourceIdToClientPortToRpcContextMapMap.get(resourceId);
		clientPortToRpcContextMap.put(clientPort, this);
	}

	public void release()
	{
		Integer clientPort = getClientPort(channel);
		if (channelToRpcContextMap != null)
		{
			channelToRpcContextMap = null;
		}
		if (role == Role.transaction_manager_client && clientPortToRpcContextMap != null)
		{
			clientPortToRpcContextMap.remove(clientPort);
			clientPortToRpcContextMap = null;
		}
		if (role == Role.resource_manager_client && resourceIdToClientPortToRpcContextMapMap != null)
		{
			for (Map<Integer, RpcContext> map : resourceIdToClientPortToRpcContextMapMap.values())
			{
				map.remove(clientPort);
			}
			resourceIdToClientPortToRpcContextMapMap = null;
		}
		if (null != resourceIdSet)
		{
			resourceIdSet.clear();
		}
	}

}
